import * as THREE from 'three';
import { Tool } from './tool';

interface Chunk {
  mesh: THREE.Mesh;
  dirty: boolean;
  iStart: number;
  iEnd: number;
  jStart: number;
  jEnd: number;
}

export class Workpiece {
  private xSize: number;
  private ySize: number;
  private zSize: number;
  private resolution: number;
  private nX: number;
  private nY: number;
  private heightMap: Float32Array;
  private chunkSize: number;
  private chunksX: number;
  private chunksY: number;
  public chunks: Chunk[];
  private material: THREE.MeshPhongMaterial;

  constructor(xSize: number, ySize: number, zSize: number, resolution: number) {
    this.xSize = xSize;
    this.ySize = ySize;
    this.zSize = zSize;
    this.resolution = resolution;
    this.nX = Math.floor(xSize / resolution) + 1;
    this.nY = Math.floor(ySize / resolution) + 1;
    this.heightMap = new Float32Array(this.nX * this.nY).fill(zSize);
    this.chunkSize = 64;
    this.chunksX = Math.ceil(this.nX / this.chunkSize);
    this.chunksY = Math.ceil(this.nY / this.chunkSize);
    this.chunks = [];
    this.material = new THREE.MeshPhongMaterial({ color: 0x808080 });

    this.initChunks();
  }

  private initChunks() {
    for (let cy = 0; cy < this.chunksY; cy++) {
      for (let cx = 0; cx < this.chunksX; cx++) {
        const iStart = cx * this.chunkSize;
        const iEnd = Math.min((cx + 1) * this.chunkSize, this.nX);
        const jStart = cy * this.chunkSize;
        const jEnd = Math.min((cy + 1) * this.chunkSize, this.nY);
        const geometry = this.createChunkGeometry(iStart, iEnd, jStart, jEnd);
        const mesh = new THREE.Mesh(geometry, this.material);
        this.chunks.push({ mesh, dirty: false, iStart, iEnd, jStart, jEnd });
      }
    }
  }

  private createChunkGeometry(iStart: number, iEnd: number, jStart: number, jEnd: number): THREE.BufferGeometry {
    const nx = iEnd - iStart;
    const ny = jEnd - jStart;
    const totalTopVertices = (nx + 1) * (ny + 1);
    const positions = new Float32Array(totalTopVertices * 2 * 3);
    let posIndex = 0;

    // Top vertices
    for (let j = jStart; j <= jEnd; j++) {
      for (let i = iStart; i <= iEnd; i++) {
        const h = this.heightMap[i + j * this.nX];
        positions[posIndex++] = i * this.resolution;
        positions[posIndex++] = j * this.resolution;
        positions[posIndex++] = h;
      }
    }

    // Bottom vertices
    for (let j = jStart; j <= jEnd; j++) {
      for (let i = iStart; i <= iEnd; i++) {
        positions[posIndex++] = i * this.resolution;
        positions[posIndex++] = j * this.resolution;
        positions[posIndex++] = 0;
      }
    }

    const indices: number[] = [];

    // Top surface
    for (let j = jStart; j < jEnd; j++) {
      for (let i = iStart; i < iEnd; i++) {
        const a = (j - jStart) * (nx + 1) + (i - iStart);
        const b = (j - jStart) * (nx + 1) + (i + 1 - iStart);
        const c = (j + 1 - jStart) * (nx + 1) + (i - iStart);
        const d = (j + 1 - jStart) * (nx + 1) + (i + 1 - iStart);
        indices.push(a, b, d, a, d, c);
      }
    }

    // Bottom surface
    for (let j = jStart; j < jEnd; j++) {
      for (let i = iStart; i < iEnd; i++) {
        const a = totalTopVertices + (j - jStart) * (nx + 1) + (i - iStart);
        const b = totalTopVertices + (j - jStart) * (nx + 1) + (i + 1 - iStart);
        const c = totalTopVertices + (j + 1 - jStart) * (nx + 1) + (i - iStart);
        const d = totalTopVertices + (j + 1 - jStart) * (nx + 1) + (i + 1 - iStart);
        indices.push(a, c, d, a, d, b);
      }
    }

    // Vertical faces (x direction)
    for (let j = jStart; j <= jEnd; j++) {
      for (let i = iStart; i < iEnd; i++) {
        const topLeft = (j - jStart) * (nx + 1) + (i - iStart);
        const topRight = (j - jStart) * (nx + 1) + (i + 1 - iStart);
        const bottomLeft = totalTopVertices + (j - jStart) * (nx + 1) + (i - iStart);
        const bottomRight = totalTopVertices + (j - jStart) * (nx + 1) + (i + 1 - iStart);
        indices.push(bottomLeft, topLeft, bottomRight, topLeft, topRight, bottomRight);
      }
    }

    // Vertical faces (y direction)
    for (let i = iStart; i <= iEnd; i++) {
      for (let j = jStart; j < jEnd; j++) {
        const topFront = (j - jStart) * (nx + 1) + (i - iStart);
        const topBack = (j + 1 - jStart) * (nx + 1) + (i - iStart);
        const bottomFront = totalTopVertices + (j - jStart) * (nx + 1) + (i - iStart);
        const bottomBack = totalTopVertices + (j + 1 - jStart) * (nx + 1) + (i - iStart);
        indices.push(bottomFront, topFront, bottomBack, topFront, topBack, bottomBack);
      }
    }

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.setIndex(indices);
    geometry.computeVertexNormals();
    return geometry;
  }

  cut(tool: Tool) {
    const minX = Math.floor((tool.posX - tool.toolRadius - this.resolution) / this.resolution);
    const minY = Math.floor((tool.posY - tool.toolRadius - this.resolution) / this.resolution);
    const maxX = Math.ceil((tool.posX + tool.toolRadius + this.resolution) / this.resolution);
    const maxY = Math.ceil((tool.posY + tool.toolRadius + this.resolution) / this.resolution);
    const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value));
    const minXClamped = clamp(minX, 0, this.nX - 1);
    const maxXClamped = clamp(maxX, 0, this.nX);
    const minYClamped = clamp(minY, 0, this.nY - 1);
    const maxYClamped = clamp(maxY, 0, this.nY);
    const toolRadiusSq = tool.toolRadius * tool.toolRadius;

    let changed = false;

    for (let y = minYClamped; y < maxYClamped; y++) {
      const chunkYM = Math.floor((y - 1) / this.chunkSize);
      const chunkY = Math.floor(y / this.chunkSize);
      const chunkYP = Math.floor((y + 1) / this.chunkSize);
      for (let x = minXClamped; x < maxXClamped; x++) {
        const chunkXM = Math.floor((x - 1) / this.chunkSize);
        const chunkX = Math.floor(x / this.chunkSize);
        const chunkXP = Math.floor((x + 1) / this.chunkSize);
        const mmX = (x + 0.5) * this.resolution;
        const mmY = (y + 0.5) * this.resolution;
        const dx = mmX - tool.posX;
        const dy = mmY - tool.posY;
        const dxdy = dx * dx + dy * dy;

        if (dxdy <= toolRadiusSq) {
          let height = tool.getToolTouchPoint(mmX, mmY);
          if (height < 0) height = 0;
          if (height < this.zSize) {
            const ndex = x + y * this.nX;
            const currentHeight = this.heightMap[ndex];
            if (!isNaN(height) && currentHeight > 0 && height < currentHeight) {
              this.heightMap[ndex] = height;
              changed = true;
              this.setChunkDirty(chunkX, chunkY);
              if (chunkXP !== chunkX) this.setChunkDirty(chunkXP, chunkY);
              if (chunkXM !== chunkX) this.setChunkDirty(chunkXM, chunkY);
              if (chunkYP !== chunkY) this.setChunkDirty(chunkX, chunkYP);
              if (chunkYM !== chunkY) this.setChunkDirty(chunkX, chunkYM);
            }
          }
        }
      }
    }
  }

  private setChunkDirty(chunkX: number, chunkY: number) {
    const chunkIndex = chunkX + chunkY * this.chunksX;
    if (chunkIndex >= 0 && chunkIndex < this.chunks.length) {
      this.chunks[chunkIndex].dirty = true;
    }
  }

  update() {
    this.chunks.forEach(chunk => {
      if (chunk.dirty) {
        const geometry = chunk.mesh.geometry;
        const positions = geometry.attributes.position.array as Float32Array;
        let posIndex = 0;
        for (let j = chunk.jStart; j <= chunk.jEnd; j++) {
          for (let i = chunk.iStart; i <= chunk.iEnd; i++) {
            const h = this.heightMap[i + j * this.nX];
            positions[posIndex + 2] = h;
            posIndex += 3;
          }
        }
        posIndex += 3 * (chunk.jEnd - chunk.jStart + 1) * (chunk.iEnd - chunk.iStart + 1);
        geometry.attributes.position.needsUpdate = true;
        geometry.computeVertexNormals();
        chunk.dirty = false;
      }
    });
  }
}